r"""
Evaluating a String in Sage
"""

# ****************************************************************************
#       Copyright (C) 2006 William Stein <wstein@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#                  https://www.gnu.org/licenses/
# ****************************************************************************

from copy import copy
import sage.repl.preparse as preparser


def sage_eval(source, locals=None, cmds='', preparse=True):
    r"""
    Obtain a Sage object from the input string by evaluating it using
    Sage. This means calling eval after preparsing and with globals
    equal to everything included in the scope of ``from sage.all
    import *``.).

    INPUT:


    -  ``source`` - a string or object with a _sage_
       method

    -  ``locals`` - evaluate in namespace of sage.all plus
       the locals dictionary

    -  ``cmds`` - string; sequence of commands to be run
       before source is evaluated.

    -  ``preparse`` - (default: True) if True, preparse the
       string expression.


    EXAMPLES: This example illustrates that preparsing is applied.

    ::

        sage: eval('2^3')
        1
        sage: sage_eval('2^3')
        8

    However, preparsing can be turned off.

    ::

        sage: sage_eval('2^3', preparse=False)
        1

    Note that you can explicitly define variables and pass them as the
    second option::

        sage: x = PolynomialRing(RationalField(),"x").gen()
        sage: sage_eval('x^2+1', locals={'x':x})
        x^2 + 1

    This example illustrates that evaluation occurs in the context of
    ``from sage.all import *``. Even though bernoulli has
    been redefined in the local scope, when calling
    ``sage_eval`` the default value meaning of bernoulli
    is used. Likewise for QQ below.

    ::

        sage: bernoulli = lambda x : x^2
        sage: bernoulli(6)
        36
        sage: eval('bernoulli(6)')
        36
        sage: sage_eval('bernoulli(6)')
        1/42

    ::

        sage: QQ = lambda x : x^2
        sage: QQ(2)
        4
        sage: sage_eval('QQ(2)')
        2
        sage: parent(sage_eval('QQ(2)'))
        Rational Field

    This example illustrates setting a variable for use in evaluation.

    ::

        sage: x = 5
        sage: eval('4//3 + x', {'x': 25})
        26
        sage: sage_eval('4/3 + x',  locals={'x': 25})
        79/3

    You can also specify a sequence of commands to be run before the
    expression is evaluated::

        sage: sage_eval('p', cmds='K.<x> = QQ[]\np = x^2 + 1')
        x^2 + 1

    If you give commands to execute and a dictionary of variables, then
    the dictionary will be modified by assignments in the commands::

        sage: vars = {}
        sage: sage_eval('None', cmds='y = 3', locals=vars)
        sage: vars['y'], parent(vars['y'])
        (3, Integer Ring)

    You can also specify the object to evaluate as a tuple. A 2-tuple
    is assumed to be a pair of a command sequence and an expression; a
    3-tuple is assumed to be a triple of a command sequence, an
    expression, and a dictionary holding local variables. (In this
    case, the given dictionary will not be modified by assignments in
    the commands.)

    ::

        sage: sage_eval(('f(x) = x^2', 'f(3)'))
        9
        sage: vars = {'rt2': sqrt(2.0)}
        sage: sage_eval(('rt2 += 1', 'rt2', vars))
        2.41421356237309
        sage: vars['rt2']
        1.41421356237310

    This example illustrates how ``sage_eval`` can be
    useful when evaluating the output of other computer algebra
    systems.

    ::

        sage: R.<x> = PolynomialRing(RationalField())
        sage: gap.eval('R:=PolynomialRing(Rationals,["x"]);')
        'Rationals[x]'
        sage: ff = gap.eval('x:=IndeterminatesOfPolynomialRing(R);; f:=x^2+1;'); ff
        'x^2+1'
        sage: sage_eval(ff, locals={'x':x})
        x^2 + 1
        sage: eval(ff)
        Traceback (most recent call last):
        ...
        RuntimeError: Use ** for exponentiation, not '^', which means xor
        in Python, and has the wrong precedence.

    Here you can see eval simply will not work but
    ``sage_eval`` will.

    TESTS:

    We get a nice minimal error message for syntax errors, that still
    points to the location of the error (in the input string)::

        sage: sage_eval('RR(22/7]')
        Traceback (most recent call last):
        ...
         File "<string>", line 1
            RR(Integer(22)/Integer(7)]
                                     ^
        SyntaxError: ...

    ::

        sage: sage_eval('None', cmds='$x = $y[3] # Does Perl syntax work?')
        Traceback (most recent call last):
        ...
         File "<string>", line 1
            $x = $y[Integer(3)] # Does Perl syntax work?
            ^
        SyntaxError: invalid ...
    """
    if isinstance(source, (list, tuple)):
        cmds = source[0]
        if len(source) > 2:
            locals = copy(source[2])
        source = source[1]

    if not isinstance(source, str):
        raise TypeError("source must be a string.")

    if locals is None:
        locals = {}

    import sage.all
    if len(cmds):
        cmd_seq = cmds + '\n_sage_eval_returnval_ = ' + source
        if preparse:
            cmd_seq = preparser.preparse_file(cmd_seq)
    else:
        if preparse:
            source = preparser.preparse(source)

    if len(cmds):
        exec(cmd_seq, sage.all.__dict__, locals)
        return locals['_sage_eval_returnval_']
    else:
        return eval(source, sage.all.__dict__, locals)



def sageobj(x, vars=None):
    """
    Return a native Sage object associated to x, if possible and
    implemented.

    If the object has an _sage_ method it is called and the value is
    returned. Otherwise str is called on the object, and all preparsing
    is applied and the resulting expression is evaluated in the context
    of ``from sage.all import *``. To evaluate the
    expression with certain variables set, use the vars argument, which
    should be a dictionary.

    EXAMPLES::

        sage: type(sageobj(gp('34/56')))
        <class 'sage.rings.rational.Rational'>
        sage: n = 5/2
        sage: sageobj(n) is n
        True
        sage: k = sageobj('Z(8^3/1)', {'Z':ZZ}); k
        512
        sage: type(k)
        <class 'sage.rings.integer.Integer'>

    This illustrates interfaces::

        sage: f = gp('2/3')
        sage: type(f)
        <class 'sage.interfaces.gp.GpElement'>
        sage: f._sage_()
        2/3
        sage: type(f._sage_())
        <class 'sage.rings.rational.Rational'>
        sage: a = gap(939393/2433)
        sage: a._sage_()
        313131/811
        sage: type(a._sage_())
        <class 'sage.rings.rational.Rational'>
    """
    try:
       return x._sage_()
    except (TypeError, NotImplementedError, AttributeError):
       return sage_eval(str(x), vars)
